/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.gameactor

import org.scalatest.{BeforeAndAfterAll, WordSpec}
import akka.testkit.{TestActorRef, ImplicitSender, TestKit}
import org.scalatest.matchers.ShouldMatchers
import akka.actor._
import jmmo.engine.property._
import jmmo.engine.multifacetedactor.MultifacetedActorSpec

/**
 * Tests actor abilities to subscribe and unsubscribe to [[akka.event.EventStream]].
 * @author Tomas Shestakov
 * @param _system actor system for testing
 * @param creator a function which returns new instance of [[jmmo.engine.gameactor.ObservableActor]]
 */
class ObservableActorSpec(_system: ActorSystem, creator: => ObservableActor, shutdown: Boolean = false)
  extends TestKit(_system) with ShouldMatchers with ImplicitSender with WordSpec with BeforeAndAfterAll {

  def this() = this(ActorSystem("TestSpec"), new ObservableActor {}, true)

  override def afterAll {
    if (shutdown) system.shutdown()
  }

  val prop1 = Property(0, Identifier('name -> "prop1"));
  val prop2 = Property("a", Identifier('name -> "prop2"));

  val obsRole = new ObservableRole with ModifiableContainerSupport {
    def id = Identifier('name -> "obsRole")
    def receive = jmmo.util.emptyPartial
    override def toString = id.attribute[String]('name).getOrElse("No name")
  }

  val obsActor = TestActorRef[ObservableActor](Props(creator))

  override val nestedSuites = {
    List(new MultifacetedActorSpec(system, creator))
  }

  "An ObservableActor" should {

    "fit MultifacetedActorSpec" in {}

    "send `ObservableEvent` to subcribed actors on add observable roles" in {
      obsActor ! ObservableContainerRequest(ObservableRequest.Action.Subscribe)

      obsActor.underlyingActor.addRole(obsRole)
      expectMsg(ObservableEvent(Identifier(), AddAlteration(obsRole.serializedCopy())))

      obsRole.addObservable(prop1)
      expectMsg(ObservableEvent(Identifier('name -> "obsRole"), AddAlteration(Property(0,Identifier('name -> "prop1")).serializedCopy())))
  }

    "send `ObservableEvent` to subcribed actors on change observable roles" in {
      prop1() = 1
//      val msg = expectMsgType[AnyRef]
//      info(msg.toString)
      expectMsg(ObservableEvent(Identifier('name -> "prop1"), ChangeAlteration(1), Identifier('name -> "obsRole")))
    }

    "send nothing to subcribed with filter actors if observable or ChangeEvent not satisfy the filter" in {
      obsActor ! FilteredObservableContainerRequest(ObservableRequest.Action.Subscribe, _.isInstanceOf[AddOrRemoveObsEvent], Identifiable.passAll)
      obsRole.addObservable(prop2)
      expectMsg(ObservableEvent(Identifier('name -> "obsRole"), AddAlteration(Property("a",Identifier('name -> "prop2")).serializedCopy())))

      prop2() = "b"
      expectNoMsg()

      obsActor ! FilteredObservableContainerRequest(ObservableRequest.Action.Subscribe, ChangeEvent.passAll,
        (obs) => obs.isInstanceOf[ObservableContainer] || obs.id.attribute[String]('name).getOrElse("") == "prop1")
      prop1() = 2
      expectMsg(ObservableEvent(Identifier('name -> "prop1"), ChangeAlteration(2), Identifier('name -> "obsRole")))
      prop2() = "c"
      expectNoMsg()
    }

    "send ObservableValueResponse as reply on ObservableValueRequest message with action Get" in {
      obsActor ! ObservableValueRequest(ObservableRequest.Action.Get, Identifier('name -> "obsRole"), Identifier('name -> "prop1"))
      expectMsg(ObservableValueResponse(2, Identifier('name -> "obsRole"), Identifier('name -> "prop1")))
    }

    "send ObservableContainerResponse as reply on ObservableContainerRequest message with action Get" in {
      obsActor ! ObservableContainerRequest(ObservableRequest.Action.Get, Identifier('name -> "obsRole"))
      expectMsg(ObservableContainerResponse(SilentObservableContainer(Iterable(SilentObservableValue(2, Identifier('name -> "prop1")), SilentObservableValue("c", Identifier('name -> "prop2"))), Identifier('name -> "obsRole")), Identifier('name -> "obsRole")))
    }

    "send nothing to unsubcribed actors" in {
      obsActor ! ObservableContainerRequest(ObservableRequest.Action.Unsubscribe)

      prop1() = 3
      expectNoMsg()
    }

    "send `ObservableEvent` to subcribed actors on remove observable roles" in {
      obsActor ! ObservableContainerRequest(ObservableRequest.Action.Subscribe)

      obsRole.removeObservable(prop1)
      expectMsg(ObservableEvent(Identifier('name -> "obsRole"), RemoveAlteration(Property(3,Identifier('name -> "prop1")).serializedCopy())))
    }
  }
}
